/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						       keyword_replacer.cpp

	$Header: /game/keyword_replacer.cpp $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "keyword_replacer.h"
#include "replace_keywords.h"

#include <ctype.h>

#include "creature_traits.h"
#include "external_string.h"
#include "format_string.h"
#include "material_array.h"
#include "material_names.h"
#include "skill.h"
#include "skill_properties.h"

// ------------------------------------------------------------------------
// class which replaces keywords with text
// ------------------------------------------------------------------------
extern t_external_string const k_text_and;
t_external_string const k_text_and( "and.dialog" );
t_external_string const k_text_the( "the.dialog" );

// ------------------------------------------------------------------------
// class which replaces keywords with text
// ------------------------------------------------------------------------
t_keyword_replacer::~t_keyword_replacer()
{
}

// ------------------------------------------------------------------------
// class which replaces keywords with text
// ------------------------------------------------------------------------
void t_keyword_replacer::add_material( int amount, t_material material )
{
	std::string amount_string;
	format_string( "%i", amount ).swap( amount_string );

	std::string material_name = k_material_name[material];

	std::string material_string = amount_string;
	material_string += ' ';
	material_string += material_name;

	m_material_count++;
	add_to_list( "%material_name_list", m_material_count, material_name );
	add_to_list( "%material_list", m_material_count, material_string );

	m_map["%material"].swap( material_string );
	m_map["%material_amount"].swap( amount_string );
	m_map["%material_name"].swap( material_name );
}

// ------------------------------------------------------------------------
// class which replaces keywords with text
// ------------------------------------------------------------------------
void t_keyword_replacer::add_materials( t_material_array const& materials )
{
	int i;

	for (i = t_material_array::k_size - 1; i >= 0; i--)
	{
		if (materials[i] > 0)
			add_material( materials[i], t_material( i ));
	}
}

// ------------------------------------------------------------------------
// class which replaces keywords with text
// ------------------------------------------------------------------------
void t_keyword_replacer::add_creature( int amount, t_creature_type creature, bool approximate )
{
	std::string creature_name;
	std::string a_creature_name;
	std::string the_creature_name;

	if (approximate)
	{
		get_creature_name( creature, 2, false ).swap( creature_name );

		get_army_size_name( amount, k_article_a ).swap( a_creature_name );
		a_creature_name += ' ';
		a_creature_name += creature_name;

		get_army_size_name( amount, k_article_the ).swap( the_creature_name );
		the_creature_name += ' ';
		the_creature_name += creature_name;
	}
	else
	{
		get_creature_name( creature, amount, false ).swap( creature_name );

		format_string( "%i ", amount ).swap( a_creature_name );
		a_creature_name += creature_name;
		
		if (amount == 1)
			get_creature_name( creature, 1, true ).swap( the_creature_name );
		else
		{
			the_creature_name = k_text_the;
			the_creature_name += format_string( " %i ", amount );
			the_creature_name += creature_name;
		}
	}
	m_creature_count++;
	m_map["%creature_name"] = get_creature_name( creature, 2, false );
	add_to_list( "%creatures", m_creature_count, a_creature_name );
	add_to_list( "%the_creatures", m_creature_count, the_creature_name );
}

// ------------------------------------------------------------------------
// Mix with add_creature, only with a hero (for text like "a few Air Elementals
// and Ryan")
// ------------------------------------------------------------------------
void t_keyword_replacer::add_hero_to_creature_list( std::string const & hero_name )
{
	m_creature_count++;

	m_map["%creature_name"] = hero_name;
	add_to_list( "%creatures", m_creature_count, hero_name );
	add_to_list( "%the_creatures", m_creature_count, hero_name );
}

// ------------------------------------------------------------------------
// class which replaces keywords with text
// add a string to a list of strings, adding commas and "and" as necessary
// ------------------------------------------------------------------------
void t_keyword_replacer::add_to_list( std::string const& keyword, int count, 
									  std::string const& text )
{
	if (count == 1)
	{
		m_map[keyword] = text;
		return;
	}

	t_map::iterator item = m_map.find( keyword );

	assert( item != m_map.end() );

	std::string new_list = text;
	if (count == 2)
	{
		new_list += ' ';
		new_list += k_text_and;
		new_list += ' ';
	}
	else
		new_list += ", ";
	new_list += item->second;

	item->second.swap( new_list );
}


// DO NOT LOCALIZE THIS STRING
// If the % key starts with this, force each word in the replacement
// string to be capitalized. Note: Any non-alphanumeric characters
// in this string must test true in is_character_part_of_word()
std::string const k_capitalize_marker = "%capitalize_";


// ------------------------------------------------------------------------
// Capitalize every word in a string
// Note: Yes, this function's test for "words" is slightly different than 
// the word test in t_keyword_replacer::operator()(). I'm not sure this is
// a bad thing. For instance, if you have "%Color's Army" in 
// t_keyword_replacer::operator()(), it will replace "%Color", not "%Color's". 
// But if you capitalize "red's army", you will get "Red's Army", not
// "Red'S Army". 
// ------------------------------------------------------------------------
std::string capitalize_words( std::string const & text )
{
	bool last_was_whitespace = true; 
	std::string result;

	result.resize( text.size() );

	int i;

	for ( i = 0; i < text.length(); i++ )
	{
		if ( last_was_whitespace )
		{
			result[i] = toupper( text[i] );
		}
		else
		{
			result[i] = text[i];
		}

		last_was_whitespace = ( isspace( text[i] ) != 0 );
	}

	return result;
}

// ------------------------------------------------------------------------
// class which replaces keywords with text
// ------------------------------------------------------------------------
std::string t_keyword_replacer::replace_word( std::string const& word ) const
{
	if (word[0] != '%')
		return word;
	if (strcmp( word.c_str(), "%%" ) == 0)
		return "%";

	std::string lookup_word = word;
	bool        capitalize;

	if ( string_insensitive_compare( k_capitalize_marker, lookup_word.substr( 0, k_capitalize_marker.length() ) ) == 0 )
	{
		lookup_word.erase( 1, k_capitalize_marker.length() - 1 );
		capitalize = true;
	}
	else
	{
		capitalize = false;
	}

	t_map::const_iterator item = m_map.find( lookup_word );

	if (item == m_map.end())
		return word;

	std::string result = item->second;

	if ( isupper( lookup_word[1] ) && !result.empty() )
		result[0] = toupper( result[0] );

	if ( capitalize )
	{
		result = capitalize_words( result );
	}

	return result;
}


// ------------------------------------------------------------------------
// The function that t_keyword_replacer::operator() uses to determine the
// boundaries of a word. Must include % and all the characters in 
// k_capitalize_marker. Not the same as !isspace(), because we want
// to able to put punctuation marks after words and not have them 
// be replaced
// ------------------------------------------------------------------------
static inline bool
is_character_part_of_word( char character )
{
	return (character >= 0 && isalnum( character )) ||	character == '_' ||	character == '%';
}

// ------------------------------------------------------------------------
// class which replaces keywords with text
// ------------------------------------------------------------------------
std::string t_keyword_replacer::operator()( std::string const& source ) const
{
#ifndef NDEBUG
	int i;
	for ( i = 0; i < k_capitalize_marker.length(); i++ )
	{
		assert( is_character_part_of_word( k_capitalize_marker[i] ) );
	}
#endif

	std::string	                result;
	std::string::const_iterator source_ptr = source.begin();
	std::string::const_iterator source_end = source.end();

	result.reserve( source.size() );
	while ( source_ptr != source_end )
	{
		// copy spaces
		while (		source_ptr != source_end
				&&	!is_character_part_of_word( *source_ptr ) )
			result += *source_ptr++;

		if ( source_ptr == source_end )
			break;

		std::string word;

		while (		source_ptr != source_end
				&&	is_character_part_of_word( *source_ptr ) )
			word += *source_ptr++;

		result += replace_word( word );
	}
	return result;
}

// ------------------------------------------------------------------------
// replace keywords
// ------------------------------------------------------------------------
std::string replace_keywords( std::string const& format_string, std::string const& keyword,
							  std::string const& text )
{
	t_keyword_replacer replacer;

	replacer.add_keyword( keyword, text );
	return replacer( format_string );
}

// ------------------------------------------------------------------------
// replace keywords
// ------------------------------------------------------------------------
std::string replace_keywords( std::string const& format_string,
							  std::string const& keyword_1, std::string const& text_1, 
							  std::string const& keyword_2, std::string const& text_2 )
{
	t_keyword_replacer replacer;

	replacer.add_keyword( keyword_1, text_1 );
	replacer.add_keyword( keyword_2, text_2 );
	return replacer( format_string );
}

// ------------------------------------------------------------------------
// replace keywords
// ------------------------------------------------------------------------
std::string replace_keywords( std::string const& format_string,
							  std::string const& keyword_1, std::string const& text_1, 
							  std::string const& keyword_2, std::string const& text_2,
							  std::string const& keyword_3, std::string const& text_3 )
{
	t_keyword_replacer replacer;

	replacer.add_keyword( keyword_1, text_1 );
	replacer.add_keyword( keyword_2, text_2 );
	replacer.add_keyword( keyword_3, text_3 );
	return replacer( format_string );
}

// ------------------------------------------------------------------------
// replace keywords
// ------------------------------------------------------------------------
std::string replace_keywords( std::string const& format_string, int amount, t_material material )
{
	t_keyword_replacer replacer;

	replacer.add_material( amount, material );
	return replacer( format_string );
}

// ------------------------------------------------------------------------
// replace keywords
// ------------------------------------------------------------------------
std::string replace_keywords( std::string const& format_string, 
							  t_material_array const& materials )
{
	t_keyword_replacer replacer;

	replacer.add_materials( materials );
	return replacer( format_string );
}

// ------------------------------------------------------------------------
// replace keywords
// ------------------------------------------------------------------------
std::string replace_keywords( std::string const& text, int amount, t_creature_type creature,
							  bool approximate )
{
	t_keyword_replacer replacer;

	replacer.add_creature( amount, creature, approximate );
	return replacer( text );
}

// ------------------------------------------------------------------------
// replace keywords
// ------------------------------------------------------------------------
std::string replace_keywords( std::string const& text, t_skill_type skill, 
							  t_skill_mastery mastery )
{
	return replace_keywords( text, t_skill( skill, mastery ));
}

// ------------------------------------------------------------------------
// replace keywords
// ------------------------------------------------------------------------
std::string replace_keywords( std::string const& text, t_skill const& skill )
{
	return replace_keywords( text, "%skill_name", get_name( skill ));
}
